package com.yzq.manager.WebSocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

import com.yzq.manager.WebSocket.disruptor.PictureEditEventProducer;
import com.yzq.manager.WebSocket.model.*;
import com.yzq.model.vo.UserVO;
import com.yzq.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 图片编辑 WebSocket 处理器
 * 负责处理图片协同编辑的 WebSocket 连接、消息接收和广播
 */
@Component
@Slf4j
public class PictureEditHandler extends TextWebSocketHandler {


    /**
     * 图片编辑事件生产者，用于将编辑事件发布到 Disruptor 队列中进行异步处理
     * 使用 @Lazy 注解延迟加载，避免循环依赖
     */
    @Resource
    @Lazy
    private PictureEditEventProducer pictureEditEventProducer;

    /**
     * 每张图片的编辑状态映射
     * key: pictureId (图片ID)
     * value: 当前正在编辑该图片的用户 ID
     * 用于确保同一时间只有一位用户可以编辑图片
     */
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();

    /**
     * 保存所有连接到 WebSocket 的会话信息
     * key: pictureId (图片ID)
     * value: 连接到该图片的所有用户会话集合
     * 用于向同一张图片的所有在线用户广播消息
     */
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();

    /**
     * 当 WebSocket 连接建立成功时调用
     * 1. 将会话保存到 pictureSessions 映射中
     * 2. 向所有连接到同一图片的用户广播新用户加入的消息
     *
     * @param session WebSocket 会话对象
     * @throws Exception 异常
     */
    @Override
    public void afterConnectionEstablished(@NotNull WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        // 从会话属性中获取用户信息和图片ID
        UserVO user = (UserVO) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        log.info("用户[{}]连接WebSocket，图片ID:{}", user.getUserName(), pictureId);
        
        // 初始化该图片的会话集合（如果不存在）
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        // 将当前会话添加到该图片的会话集合中
        pictureSessions.get(pictureId).add(session);
        
        // 构造响应消息，通知其他用户有新用户加入编辑
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("用户 %s 加入编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(user);
        
        // 广播给所有连接到该图片的用户
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    /**
     * 当收到前端发送的文本消息时调用
     * 1. 解析消息内容为 PictureEditRequestMessage 对象
     * 2. 从会话属性中获取用户信息和图片ID
     * 3. 将消息发布到 Disruptor 队列中进行异步处理
     *
     * @param session WebSocket 会话对象
     * @param message 收到的文本消息
     * @throws Exception 异常
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        // 使用 Hutool 工具将 JSON 字符串转换为 PictureEditRequestMessage 对象
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        // 从会话属性中获取用户信息和图片ID
        UserVO user = (UserVO) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        log.info("用户[{}]发送消息：{}", user.getUserName(), message.getPayload());
        // 将编辑请求消息发布到 Disruptor 队列中，由消费者异步处理
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);
    }

    /**
     * 处理用户进入编辑状态的请求
     * 只有当没有其他用户正在编辑该图片时，才允许当前用户进入编辑状态
     *
     * @param pictureEditRequestMessage 图片编辑请求消息
     * @param session                   WebSocket 会话对象
     * @param user                      当前用户信息
     * @param pictureId                 图片ID
     * @throws IOException IO异常
     */
    public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, UserVO user, Long pictureId) throws IOException {

        log.info("用户 {} 进入编辑状态", user.getUserName());
        // 检查是否已经有用户在编辑该图片
        if (!pictureEditingUsers.containsKey(pictureId)) {
            // 设置当前用户为该图片的编辑者
            pictureEditingUsers.put(pictureId, user.getId());
            // 构造响应消息，通知所有用户该用户开始编辑图片
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            String message = String.format("用户 %s 开始编辑图片", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(user);
            // 广播给所有连接到该图片的用户
            broadcastToPicture(pictureId, pictureEditResponseMessage);
        }
    }

    /**
     * 处理用户的编辑操作（如放大、缩小等）
     * 只有当前编辑者才能执行编辑操作
     *
     * @param pictureEditRequestMessage 图片编辑请求消息，包含具体操作类型
     * @param session                   WebSocket 会话对象
     * @param user                      当前用户信息
     * @param pictureId                 图片ID
     * @throws IOException IO异常
     */
    public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, UserVO user, Long pictureId) throws IOException {

        log.info("用户 {} 执行编辑操作：{}", user.getUserName(), pictureEditRequestMessage.getEditAction());
        // 获取当前正在编辑该图片的用户ID
        Long editingUserId = pictureEditingUsers.get(pictureId);
        // 获取编辑动作类型
        String editAction = pictureEditRequestMessage.getEditAction();
        // 根据动作类型获取对应的枚举值
        PictureEditActionEnum actionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        if (actionEnum == null) {
            log.error("无效的编辑动作: {}", editAction);
            return;
        }
        // 验证当前用户是否为该图片的编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 构造响应消息，通知其他用户该用户执行了某个编辑操作
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            String message = String.format("%s 执行 %s", user.getUserName(), actionEnum.getText());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setEditAction(editAction);
            pictureEditResponseMessage.setUser(user);
            // 新增：传递fabric.js序列化数据
            pictureEditResponseMessage.setData(pictureEditRequestMessage.getData());
            // 广播给除了当前客户端之外的其他用户，避免重复编辑
            broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }
    }


    /**
     * 处理用户退出编辑状态的请求
     * 只有当前编辑者才能退出编辑状态
     *
     * @param pictureEditRequestMessage 图片编辑请求消息
     * @param session                   WebSocket 会话对象
     * @param user                      当前用户信息
     * @param pictureId                 图片ID
     * @throws IOException IO异常
     */
    public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, UserVO user, Long pictureId) throws IOException {
        log.info("用户 {} 退出编辑状态", user.getUserName());
        // 获取当前正在编辑该图片的用户ID
        Long editingUserId = pictureEditingUsers.get(pictureId);
        // 验证当前用户是否为该图片的编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 移除该图片的编辑状态，表示当前用户已退出编辑
            pictureEditingUsers.remove(pictureId);
            // 构造响应消息，通知所有用户该用户退出了编辑
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            String message = String.format("用户 %s 退出编辑图片", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(user);
            // 广播给所有连接到该图片的用户
            broadcastToPicture(pictureId, pictureEditResponseMessage);
        }
    }

    /**
     * 当 WebSocket 连接关闭时调用
     * 1. 处理用户退出编辑状态
     * 2. 从会话集合中移除当前会话
     * 3. 向其他用户广播该用户离开的消息
     *
     * @param session WebSocket 会话对象
     * @param status  连接关闭状态
     * @throws Exception 异常
     */
    @Override
    public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        // 从会话属性中获取用户信息和图片ID
        UserVO user = (UserVO) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        log.info("用户 {} 退出编辑", user.getUserName());
        // 处理用户退出编辑状态（即使没有显式发送退出消息）
        handleExitEditMessage(null, session, user, pictureId);
        // 从会话集合中移除当前会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (sessionSet != null) {
            sessionSet.remove(session);
            // 如果该图片已无任何连接，移除整个会话集合
            if (sessionSet.isEmpty()) {
                pictureSessions.remove(pictureId);
            }
        }
        // 构造响应消息，通知其他用户该用户离开了编辑
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("用户 %s 离开编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(user);
        // 广播给所有连接到该图片的用户
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    /**
     * 向指定图片的所有连接用户广播消息（支持排除特定会话）
     * 用于避免向消息发送者自身重复发送消息
     *
     * @param pictureId                  图片ID
     * @param pictureEditResponseMessage 要广播的响应消息
     * @param excludeSession             需要排除的会话（通常为消息发送者）
     * @throws IOException IO异常
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws IOException {
        log.info("广播图片 {} 的消息", pictureId);
        // 获取连接到该图片的所有会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (CollUtil.isNotEmpty(sessionSet)) {
            // 创建 ObjectMapper 用于序列化消息
            ObjectMapper objectMapper = new ObjectMapper();
            // 配置序列化规则：将 Long 类型序列化为 String，避免前端精度丢失
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance); // 支持 long 基本类型
            objectMapper.registerModule(module);
            // 将响应消息序列化为 JSON 字符串
            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);
            // 遍历所有会话并发送消息
            for (WebSocketSession session : sessionSet) {
                // 排除指定的会话（通常为消息发送者自身）
                if (session.equals(excludeSession)) {
                    continue;
                }
                // 只向已打开的会话发送消息
                if (session.isOpen()) {
                    session.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * 向指定图片的所有连接用户广播消息
     * 是 broadcastToPicture(Long, PictureEditResponseMessage, WebSocketSession) 方法的重载版本
     *
     * @param pictureId                  图片ID
     * @param pictureEditResponseMessage 要广播的响应消息
     * @throws IOException IO异常
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws IOException {
        // 调用重载方法，不排除任何会话
        broadcastToPicture(pictureId, pictureEditResponseMessage, null);
    }

    /**
     * 处理获取在线用户列表的消息
     *
     * @param pictureEditRequestMessage 消息对象
     * @param session                   WebSocket会话对象
     * @param user                      用户信息
     * @param pictureId                 图片ID
     */
    public void handleGetOnlineUsersMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, UserVO user, Long pictureId) throws IOException {
        // 获取在线用户列表
        List<OnlineUser> onlineUsers = getOnlineUsers(pictureId);
        // 构造响应消息
        PictureEditResponseMessage response = new PictureEditResponseMessage();
        response.setType(PictureEditMessageTypeEnum.ONLINE_USERS.getValue());
        response.setMessage("当前在线用户列表");
        response.setData(onlineUsers); // 存入用户列表
        // 仅返回给请求的客户端
        session.sendMessage(new TextMessage(JSONUtil.toJsonStr(response)));
    }
    /**
     * 获取指定图片的在线用户列表
     * @param pictureId 图片ID
     * @return 在线用户列表（UserVO）
     */
    public List<OnlineUser> getOnlineUsers(Long pictureId) {
        List<OnlineUser> onlineUsers = new ArrayList<>();
        // 获取该图片对应的所有会话集合
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (CollUtil.isNotEmpty(sessionSet)) {
            for (WebSocketSession session : sessionSet) {
                // 从会话属性中获取用户信息（拦截器已确保user存在）
                UserVO user = (UserVO) session.getAttributes().get("user");
                OnlineUser onlineUser = new OnlineUser();
                onlineUser.setUserId(String.valueOf(user.getId()));
                onlineUser.setUserName(user.getUserName());
                onlineUsers.add(onlineUser);
            }
        }
        return onlineUsers;
    }
}
















